home *** CD-ROM | disk | FTP | other *** search
/ Programming a Multiplayer FPS in DirectX / Programming a Multiplayer FPS in DirectX (Companion CD).iso / DirectX / dxsdk_oct2004.exe / dxsdk.exe / Samples / C++ / Direct3D / MeshFromOBJ / MeshLoader.cpp < prev    next >
Encoding:
C/C++ Source or Header  |  2004-09-27  |  17.7 KB  |  564 lines

  1. //--------------------------------------------------------------------------------------
  2. // File: MeshLoader.cpp
  3. //
  4. // Wrapper class for ID3DXMesh interface. Handles loading mesh data from an .obj file
  5. // and resource management for material textures.
  6. //
  7. // Copyright (c) Microsoft Corporation. All rights reserved.
  8. //--------------------------------------------------------------------------------------
  9. #include "dxstdafx.h"
  10. #include "meshloader.h"
  11. #include <fstream>
  12. using namespace std;
  13.  
  14.  
  15. // Vertex declaration
  16. D3DVERTEXELEMENT9 VERTEX_DECL[] =
  17. {
  18.     { 0,  0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT,  D3DDECLUSAGE_POSITION, 0}, 
  19.     { 0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT,  D3DDECLUSAGE_NORMAL,   0}, 
  20.     { 0, 24, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT,  D3DDECLUSAGE_TEXCOORD, 0},
  21.     D3DDECL_END()
  22. };
  23.  
  24.  
  25. //--------------------------------------------------------------------------------------
  26. CMeshLoader::CMeshLoader()
  27. {
  28.     m_pd3dDevice = NULL;  
  29.     m_pMesh = NULL;  
  30.  
  31.     ZeroMemory( m_strMediaDir, sizeof(m_strMediaDir) );
  32. }
  33.  
  34.  
  35. //--------------------------------------------------------------------------------------
  36. CMeshLoader::~CMeshLoader()
  37. {
  38.     Destroy();
  39. }
  40.  
  41.  
  42. //--------------------------------------------------------------------------------------
  43. void CMeshLoader::Destroy()
  44. {
  45.     for( int iMaterial=0; iMaterial < m_Materials.GetSize(); iMaterial++ )
  46.     {
  47.         Material* pMaterial = m_Materials.GetAt( iMaterial );
  48.         
  49.         // Avoid releasing the same texture twice
  50.         for( int x=iMaterial+1; x < m_Materials.GetSize(); x++ )
  51.         {
  52.             Material* pCur = m_Materials.GetAt( x );
  53.             if( pCur->pTexture == pMaterial->pTexture )
  54.                 pCur->pTexture = NULL;
  55.         }
  56.  
  57.         SAFE_RELEASE( pMaterial->pTexture );
  58.         SAFE_DELETE( pMaterial );
  59.     }
  60.  
  61.     m_Materials.RemoveAll();
  62.     m_Vertices.RemoveAll();
  63.     m_Indices.RemoveAll();
  64.     m_Attributes.RemoveAll();
  65.  
  66.     SAFE_RELEASE( m_pMesh );
  67.     m_pd3dDevice = NULL;
  68. }
  69.  
  70.  
  71. //--------------------------------------------------------------------------------------
  72. HRESULT CMeshLoader::Create( IDirect3DDevice9* pd3dDevice, const WCHAR* strFilename )
  73. {
  74.     HRESULT hr;
  75.     WCHAR str[ MAX_PATH ] = {0};
  76.  
  77.     // Start clean
  78.     Destroy();
  79.  
  80.     // Store the device pointer
  81.     m_pd3dDevice = pd3dDevice;
  82.  
  83.     // Load the vertex buffer, index buffer, and subset information from a file. In this case, 
  84.     // an .obj file was chosen for simplicity, but it's meant to illustrate that ID3DXMesh objects
  85.     // can be filled from any mesh file format once the necessary data is extracted from file.
  86.     V_RETURN( LoadGeometryFromOBJ( strFilename ) );
  87.  
  88.     // Set the current directory based on where the mesh was found
  89.     WCHAR wstrOldDir[MAX_PATH] = {0};
  90.     GetCurrentDirectory( MAX_PATH, wstrOldDir );
  91.     SetCurrentDirectory( m_strMediaDir );
  92.  
  93.     // Load material textures
  94.     for( int iMaterial=0; iMaterial < m_Materials.GetSize(); iMaterial++ )
  95.     {
  96.         Material* pMaterial = m_Materials.GetAt( iMaterial );
  97.         if( pMaterial->strTexture[0] )
  98.         {   
  99.             // Avoid loading the same texture twice
  100.             bool bFound = false;
  101.             for( int x=0; x < iMaterial; x++ )
  102.             {
  103.                 Material* pCur = m_Materials.GetAt( x );
  104.                 if( 0 == wcscmp( pCur->strTexture, pMaterial->strTexture ) )
  105.                 {
  106.                     bFound = true;
  107.                     pMaterial->pTexture = pCur->pTexture;
  108.                     break;
  109.                 }
  110.             }
  111.  
  112.             // Not found, load the texture
  113.             if( !bFound )
  114.             {
  115.                 V_RETURN( DXUTFindDXSDKMediaFileCch( str, MAX_PATH, pMaterial->strTexture ) );
  116.                 V_RETURN( D3DXCreateTextureFromFile( pd3dDevice, pMaterial->strTexture, 
  117.                                                     &(pMaterial->pTexture) ) );
  118.             }
  119.         }
  120.     }
  121.  
  122.     // Restore the original current directory
  123.     SetCurrentDirectory( wstrOldDir );
  124.  
  125.     // Create the encapsulated mesh
  126.     ID3DXMesh* pMesh = NULL;
  127.     V_RETURN( D3DXCreateMesh( m_Indices.GetSize() / 3, m_Vertices.GetSize(), 
  128.                               D3DXMESH_MANAGED | D3DXMESH_32BIT, VERTEX_DECL, 
  129.                               pd3dDevice, &pMesh ) ); 
  130.     
  131.     // Copy the vertex data
  132.     VERTEX* pVertex;
  133.     V_RETURN( pMesh->LockVertexBuffer( 0, (void**) &pVertex ) );
  134.     memcpy( pVertex, m_Vertices.GetData(), m_Vertices.GetSize() * sizeof(VERTEX) );
  135.     pMesh->UnlockVertexBuffer();
  136.     m_Vertices.RemoveAll();
  137.     
  138.     // Copy the index data
  139.     DWORD* pIndex;
  140.     V_RETURN( pMesh->LockIndexBuffer( 0, (void**) &pIndex ) );
  141.     memcpy( pIndex, m_Indices.GetData(), m_Indices.GetSize() * sizeof(DWORD) );
  142.     pMesh->UnlockIndexBuffer();
  143.     m_Indices.RemoveAll();
  144.     
  145.     // Copy the attribute data
  146.     DWORD* pSubset;
  147.     V_RETURN( pMesh->LockAttributeBuffer( 0, &pSubset ) );
  148.     memcpy( pSubset, m_Attributes.GetData(), m_Attributes.GetSize() * sizeof(DWORD) );
  149.     pMesh->UnlockAttributeBuffer();
  150.     m_Attributes.RemoveAll();
  151.  
  152.     // Reorder the vertices according to subset and optimize the mesh for this graphics 
  153.     // card's vertex cache. When rendering the mesh's triangle list the vertices will 
  154.     // cache hit more often so it won't have to re-execute the vertex shader.
  155.     DWORD* aAdjacency = new DWORD[pMesh->GetNumFaces() * 3];
  156.     if( aAdjacency == NULL )
  157.         return E_OUTOFMEMORY;
  158.  
  159.     V( pMesh->ConvertPointRepsToAdjacency(NULL, aAdjacency) );
  160.     V( pMesh->OptimizeInplace(D3DXMESHOPT_ATTRSORT | D3DXMESHOPT_VERTEXCACHE, aAdjacency, NULL, NULL, NULL) );
  161.     
  162.     SAFE_DELETE_ARRAY( aAdjacency );
  163.     m_pMesh = pMesh;
  164.  
  165.     return S_OK;
  166. }
  167.  
  168.  
  169. //--------------------------------------------------------------------------------------
  170. HRESULT CMeshLoader::LoadGeometryFromOBJ( const WCHAR* strFileName )
  171. {
  172.     WCHAR strMaterialFilename[MAX_PATH] = {0};
  173.     WCHAR wstr[MAX_PATH];
  174.     char str[MAX_PATH];
  175.     HRESULT hr;
  176.  
  177.     // Find the file
  178.     V_RETURN( DXUTFindDXSDKMediaFileCch( wstr, MAX_PATH, strFileName ) );
  179.     WideCharToMultiByte( CP_ACP, 0, wstr, -1, str, MAX_PATH, NULL, NULL );
  180.  
  181.     // Store the directory where the mesh was found
  182.     wcsncpy( m_strMediaDir, wstr, MAX_PATH-1 );
  183.     WCHAR* pch = wcsrchr( m_strMediaDir, L'\\' );
  184.     if( pch )
  185.         *pch = NULL;
  186.  
  187.     // Create temporary storage for the input data. Once the data has been loaded into
  188.     // a reasonable format we can create a D3DXMesh object and load it with the mesh data.
  189.     CGrowableArray< D3DXVECTOR3 > Positions;
  190.     CGrowableArray< D3DXVECTOR2 > TexCoords;
  191.     CGrowableArray< D3DXVECTOR3 > Normals;
  192.     
  193.     // The first subset uses the default material
  194.     Material* pMaterial = new Material();
  195.     if( pMaterial == NULL )
  196.         return E_OUTOFMEMORY;
  197.  
  198.     InitMaterial( pMaterial );
  199.     wcsncpy( pMaterial->strName, L"default", MAX_PATH-1 );
  200.     m_Materials.Add( pMaterial );
  201.  
  202.     DWORD dwCurSubset = 0;
  203.  
  204.     // File input
  205.     WCHAR strCommand[256] = {0};
  206.     wifstream InFile( str );
  207.     if( !InFile )
  208.         return DXTRACE_ERR( L"wifstream::open", E_FAIL );
  209.     
  210.     for(;;)
  211.     {
  212.         InFile >> strCommand;
  213.         if( !InFile )
  214.             break;
  215.         
  216.         if( 0 == wcscmp( strCommand, L"#" ) )
  217.         {
  218.             // Comment
  219.         }
  220.         else if( 0 == wcscmp( strCommand, L"v" ) )
  221.         {
  222.             // Vertex Position
  223.             float x, y, z;
  224.             InFile >> x >> y >> z;
  225.             Positions.Add( D3DXVECTOR3( x, y, z ) ); 
  226.         }
  227.         else if( 0 == wcscmp( strCommand, L"vt" ) )
  228.         {
  229.             // Vertex TexCoord
  230.             float u, v;
  231.             InFile >> u >> v;
  232.             TexCoords.Add( D3DXVECTOR2( u, v ) );
  233.         }
  234.         else if( 0 == wcscmp( strCommand, L"vn" ) )
  235.         {
  236.             // Vertex Normal
  237.             float x, y, z;
  238.             InFile >> x >> y >> z;
  239.             Normals.Add( D3DXVECTOR3( x, y, z ) );
  240.         }
  241.         else if( 0 == wcscmp( strCommand, L"f" ) )
  242.         {
  243.             // Face
  244.             UINT iPosition, iTexCoord, iNormal;
  245.             VERTEX vertex;
  246.             
  247.             for( UINT iFace=0; iFace < 3; iFace++ )
  248.             {
  249.                 ZeroMemory( &vertex, sizeof(VERTEX) );
  250.  
  251.                 // OBJ format uses 1-based arrays
  252.                 InFile >> iPosition;
  253.                 vertex.position = Positions[ iPosition-1 ];
  254.                 
  255.                 if( '/' == InFile.get() )
  256.                 {
  257.                     // Optional texture coordinate
  258.                     InFile >> iTexCoord;
  259.                     vertex.texcoord = TexCoords[ iTexCoord-1 ];
  260.  
  261.                     if( '/' == InFile.get() )
  262.                     {
  263.                         // Optional vertex normal
  264.                         InFile >> iNormal;
  265.                         vertex.normal = Normals[ iNormal-1 ];
  266.                     }
  267.                 }
  268.  
  269.                 // If a duplicate vertex doesn't exist, add this vertex to the Vertices
  270.                 // list. Store the index in the Indices array. The Vertices and Indices
  271.                 // lists will eventually become the Vertex Buffer and Index Buffer for
  272.                 // the mesh.
  273.                 DWORD index = AddVertex( iPosition, &vertex );
  274.                 m_Indices.Add( index );
  275.             }
  276.             m_Attributes.Add( dwCurSubset );
  277.         }
  278.         else if( 0 == wcscmp( strCommand, L"mtllib" ) )
  279.         {
  280.             // Material library
  281.             InFile >> strMaterialFilename;
  282.         }
  283.         else if( 0 == wcscmp( strCommand, L"usemtl" ) )
  284.         {
  285.             // Material
  286.             WCHAR strName[MAX_PATH] = {0};
  287.             InFile >> strName;
  288.             
  289.             bool bFound = false;
  290.             for( int iMaterial=0; iMaterial < m_Materials.GetSize(); iMaterial++ )
  291.             {
  292.                 Material* pCurMaterial = m_Materials.GetAt( iMaterial );
  293.                 if( 0 == wcscmp( pCurMaterial->strName, strName ) )
  294.                 {
  295.                     bFound = true;
  296.                     dwCurSubset = iMaterial;
  297.                     break;
  298.                 }  
  299.             }
  300.  
  301.             if( !bFound )
  302.             {
  303.                 pMaterial = new Material();
  304.                 if( pMaterial == NULL )
  305.                     return E_OUTOFMEMORY;
  306.  
  307.                 dwCurSubset = m_Materials.GetSize();
  308.  
  309.                 InitMaterial( pMaterial );
  310.                 wcsncpy( pMaterial->strName, strName, MAX_PATH-1 );
  311.  
  312.                 m_Materials.Add( pMaterial );
  313.             }
  314.         }
  315.         else
  316.         {
  317.             // Unimplemented or unrecognized command
  318.         }
  319.  
  320.         InFile.ignore( 1000, '\n' );
  321.     }
  322.  
  323.     // Cleanup
  324.     InFile.close();
  325.     DeleteCache();
  326.  
  327.     // If an associated material file was found, read that in as well.
  328.     if( strMaterialFilename[0] )
  329.     {
  330.         V_RETURN( LoadMaterialsFromMTL( strMaterialFilename ) );
  331.     }
  332.  
  333.     return S_OK;
  334. }
  335.  
  336.  
  337. //--------------------------------------------------------------------------------------
  338. DWORD CMeshLoader::AddVertex( UINT hash, VERTEX* pVertex )
  339. {
  340.     // If this vertex doesn't already exist in the Vertices list, create a new entry.
  341.     // Add the index of the vertex to the Indices list.
  342.     bool bFoundInList = false;
  343.     DWORD index = 0;
  344.  
  345.     // Since it's very slow to check every element in the vertex list, a hashtable stores
  346.     // vertex indices according to the vertex position's index as reported by the OBJ file
  347.     if( (UINT)m_VertexCache.GetSize() > hash )
  348.     {
  349.         CacheEntry* pEntry = m_VertexCache.GetAt( hash );
  350.         while( pEntry != NULL )
  351.         {
  352.             VERTEX* pCacheVertex = m_Vertices.GetData() + pEntry->index;
  353.  
  354.             // If this vertex is identical to the vertex already in the list, simply
  355.             // point the index buffer to the existing vertex
  356.             if( 0 == memcmp( pVertex, pCacheVertex, sizeof(VERTEX) ) )
  357.             {
  358.                 bFoundInList = true;
  359.                 index = pEntry->index;
  360.                 break;
  361.             }
  362.  
  363.             pEntry = pEntry->pNext;
  364.         }
  365.     }
  366.  
  367.     // Vertex was not found in the list. Create a new entry, both within the Vertices list
  368.     // and also within the hashtable cache
  369.     if( !bFoundInList )
  370.     {
  371.         // Add to the Vertices list
  372.         index = m_Vertices.GetSize();
  373.         m_Vertices.Add( *pVertex );
  374.  
  375.         // Add this to the hashtable
  376.         CacheEntry* pNewEntry = new CacheEntry;
  377.         if( pNewEntry == NULL )
  378.             return E_OUTOFMEMORY;
  379.  
  380.         pNewEntry->index = index;
  381.         pNewEntry->pNext = NULL;
  382.  
  383.         // Grow the cache if needed
  384.         while( (UINT)m_VertexCache.GetSize() <= hash )
  385.         {
  386.             m_VertexCache.Add( NULL );
  387.         }
  388.  
  389.         // Add to the end of the linked list
  390.         CacheEntry* pCurEntry = m_VertexCache.GetAt( hash );
  391.         if( pCurEntry == NULL )
  392.         {
  393.             // This is the head element
  394.             m_VertexCache.SetAt( hash, pNewEntry );
  395.         }
  396.         else
  397.         {
  398.             // Find the tail
  399.             while( pCurEntry->pNext != NULL )
  400.             {
  401.                 pCurEntry = pCurEntry->pNext;
  402.             }
  403.  
  404.             pCurEntry->pNext = pNewEntry;
  405.         } 
  406.     }
  407.  
  408.     return index;
  409. }
  410.  
  411.  
  412. //--------------------------------------------------------------------------------------
  413. void CMeshLoader::DeleteCache()
  414. {
  415.     // Iterate through all the elements in the cache and subsequent linked lists
  416.     for( int i=0; i < m_VertexCache.GetSize(); i++ )
  417.     {
  418.         CacheEntry* pEntry = m_VertexCache.GetAt( i );
  419.         while( pEntry != NULL )
  420.         {
  421.             CacheEntry* pNext = pEntry->pNext;
  422.             SAFE_DELETE( pEntry );
  423.             pEntry = pNext;
  424.         }
  425.     }
  426.  
  427.     m_VertexCache.RemoveAll();
  428. }
  429.  
  430.  
  431. //--------------------------------------------------------------------------------------
  432. HRESULT CMeshLoader::LoadMaterialsFromMTL( const WCHAR* strFileName )
  433. {
  434.     HRESULT hr;
  435.  
  436.     // Set the current directory based on where the mesh was found
  437.     WCHAR wstrOldDir[MAX_PATH] = {0};
  438.     GetCurrentDirectory( MAX_PATH, wstrOldDir );
  439.     SetCurrentDirectory( m_strMediaDir );
  440.  
  441.     // Find the file
  442.     WCHAR strPath[MAX_PATH];
  443.     char cstrPath[MAX_PATH];
  444.     V_RETURN( DXUTFindDXSDKMediaFileCch( strPath, MAX_PATH, strFileName ) );
  445.     WideCharToMultiByte( CP_ACP, 0, strPath, -1, cstrPath, MAX_PATH, NULL, NULL );
  446.  
  447.     // File input
  448.     WCHAR strCommand[256] = {0};
  449.     wifstream InFile( cstrPath );
  450.     if( !InFile )
  451.         return DXTRACE_ERR( L"wifstream::open", E_FAIL );
  452.     
  453.     // Restore the original current directory
  454.     SetCurrentDirectory( wstrOldDir );
  455.  
  456.     Material* pMaterial = NULL;
  457.  
  458.     for(;;)
  459.     {
  460.         InFile >> strCommand;
  461.         if( !InFile )
  462.             break;
  463.         
  464.         if( 0 == wcscmp( strCommand, L"newmtl" ) )
  465.         {
  466.             // Switching active materials
  467.             WCHAR strName[MAX_PATH] = {0};
  468.             InFile >> strName;
  469.             
  470.             pMaterial = NULL;
  471.             for( int i=0; i < m_Materials.GetSize(); i++ )
  472.             {
  473.                 Material* pCurMaterial = m_Materials.GetAt( i );
  474.                 if( 0 == wcscmp( pCurMaterial->strName, strName ) )
  475.                 {
  476.                     pMaterial = pCurMaterial;
  477.                     break;
  478.                 }  
  479.             }
  480.         }
  481.         
  482.         // The rest of the commands rely on an active material
  483.         if( pMaterial == NULL )
  484.             continue;
  485.  
  486.         if( 0 == wcscmp( strCommand, L"#" ) )
  487.         {
  488.             // Comment
  489.         }
  490.         else if( 0 == wcscmp( strCommand, L"Ka" ) )
  491.         {
  492.             // Ambient color
  493.             float r, g, b;
  494.             InFile >> r >> g >> b;
  495.             pMaterial->vAmbient = D3DXVECTOR3(r, g, b); 
  496.         }
  497.         else if( 0 == wcscmp( strCommand, L"Kd" ) )
  498.         {
  499.             // Diffuse color
  500.             float r, g, b;
  501.             InFile >> r >> g >> b;
  502.             pMaterial->vDiffuse = D3DXVECTOR3(r, g, b); 
  503.         }
  504.         else if( 0 == wcscmp( strCommand, L"Ks" ) )
  505.         {
  506.             // Specular color
  507.             float r, g, b;
  508.             InFile >> r >> g >> b;
  509.             pMaterial->vSpecular = D3DXVECTOR3(r, g, b); 
  510.         }
  511.         else if( 0 == wcscmp( strCommand, L"d" ) ||
  512.                 0 == wcscmp( strCommand, L"Tr" ) )
  513.         {
  514.             // Alpha
  515.             InFile >> pMaterial->fAlpha;
  516.         }
  517.         else if( 0 == wcscmp( strCommand, L"Ns" ) )
  518.         {
  519.             // Shininess
  520.             int nShininess;
  521.             InFile >> nShininess;
  522.             pMaterial->nShininess = nShininess;
  523.         }
  524.         else if( 0 == wcscmp( strCommand, L"illum" ) )
  525.         {
  526.             // Specular on/off
  527.             int illumination;
  528.             InFile >> illumination;
  529.             pMaterial->bSpecular = (illumination == 2);
  530.         }
  531.         else if( 0 == wcscmp( strCommand, L"map_Kd" ) )
  532.         {
  533.             // Texture
  534.             InFile >> pMaterial->strTexture;
  535.         }
  536.         
  537.         else
  538.         {
  539.             // Unimplemented or unrecognized command
  540.         }
  541.        
  542.         InFile.ignore( 1000, L'\n' );
  543.     }
  544.  
  545.     InFile.close();
  546.  
  547.     return S_OK;
  548. }
  549.  
  550.  
  551. //--------------------------------------------------------------------------------------
  552. void CMeshLoader::InitMaterial( Material* pMaterial )
  553. {
  554.     ZeroMemory( pMaterial, sizeof(Material) );
  555.  
  556.     pMaterial->vAmbient = D3DXVECTOR3(0.2f, 0.2f, 0.2f);
  557.     pMaterial->vDiffuse = D3DXVECTOR3(0.8f, 0.8f, 0.8f);
  558.     pMaterial->vSpecular = D3DXVECTOR3(1.0f, 1.0f, 1.0f);
  559.     pMaterial->nShininess = 0;
  560.     pMaterial->fAlpha = 1.0f;
  561.     pMaterial->bSpecular = false;
  562.     pMaterial->pTexture = NULL;
  563. }
  564.